#pragma once

#include <string>
#include <fstream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#include <filesystem>
#include <zenox/unicode.hpp>
#include <zenox/minilog.hpp>
#include <zlib.h>

namespace zenox {

inline const char *zliberror(int status) {
    switch (status) {
#define PER_ZLIB_ERR(x) case x: return #x;
    PER_ZLIB_ERR(Z_OK)
    PER_ZLIB_ERR(Z_STREAM_END)
    PER_ZLIB_ERR(Z_NEED_DICT)
    PER_ZLIB_ERR(Z_STREAM_ERROR)
    PER_ZLIB_ERR(Z_DATA_ERROR)
    PER_ZLIB_ERR(Z_MEM_ERROR)
    PER_ZLIB_ERR(Z_BUF_ERROR)
    PER_ZLIB_ERR(Z_VERSION_ERROR)
#undef PER_ZLIB_ERR
    case Z_ERRNO: return std::strerror(errno);
    }
    return "unknown";
}

template <class Arr = std::vector<char>>
inline Arr gzip_file_get_binary(std::string const &path) {
    auto filename = zenox::u8path(path).string();
    gzFile fp = gzopen(filename.c_str(), "rb");
    if (!fp) {
        zenox::fatal_error("{}: {}", path, std::strerror(errno));
        return {};
    }
    size_t mtu = 4096;
    Arr res;
    res.resize(mtu);
    while (true) {
        z_size_t n = gzfread(res.data() + (res.size() - mtu), 1, mtu, fp);
        if (n != mtu) {
            res.resize(res.size() - mtu + n);
            break;
        }
        mtu *= 2;
        res.resize(res.size() + mtu);
    }
    gzclose(fp);
    return res;
}

inline bool gzip_file_put_binary(std::string const &path, const void *arr_data, size_t arr_size) {
    const char *filename = path.c_str();
    gzFile fp = gzopen(filename, "wb");
    if (!fp) {
        zenox::fatal_error("{}: {}", path, std::strerror(errno));
        return {};
    }
    z_size_t n = gzfwrite(arr_data, arr_size, 1, fp);
    if (n != 1) {
        zenox::fatal_error("{}: {}", path, gzerror(fp, nullptr));
    }
    gzclose(fp);
    return true;
}

template <class Arr = std::vector<char>>
inline bool gzip_file_put_binary(std::string const &path, Arr const &arr) {
    return gzip_file_put_binary(std::data(arr), std::size(arr), path);
}

}
